﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;


/*
 *	Add: SidePots
 *	Add: When players suddenly quits while in game, turn
 *	Add: Send amount of player need to in case of a call.
 *	Change: order and sort the exceptions
 *  Add: first round starts after big/small and stakes are taken from them
 *  Change: FIX and check for the fullest game protocol
 * 
 * 
 */

namespace pokerServer.GameManagment.GameTools {
	public class GameEngine {

		public static uint PLAYER_TURN_TIME = 15000;
		public static uint GAMES_PLAYED = 0;

		public enum GameStates { NotStarted, Dealing, PreFlop, Flop, Turn, River, ShowHands, EndGame };
		public enum GameDecisions { Fold = 1, Check, Call, Raise, AllIn, NoChoise };

		private Table table = null;
		public List<Seat> playerList = null;
		public Hashtable seats = null;
		public Hashtable wins = null;
		public Card[] gameCards;

		public ushort indexPlayerDealer;
		public ushort indexPlayerCurrent;

		private GameStates gameState = GameStates.NotStarted;
		public GameDecisions lastCall;
		public GameDecisions lastAction;
		public ushort lastRaise;
		public Boolean firstRaise;

		public Boolean lastActionOK;

		public GameEngine(Table table) {
			this.table = table;
		}
		public void startGame() {
			Console.WriteLine("Starting Game");
			this.reset();
			this.launchGame();
			this.table.tableState = Table.TableStates.GameOn;
		}

		// setting new game
		private void reset() {
			
			this.table.PotSize = 0;
			this.playerList = this.buildPlayerList();
			this.resetGamePlayers();
			this.shiftRoles();
			this.firstRaise = true;

		}
		private void shiftRoles() {
			
			int pNum = this.playerList.Count;
			this.table.gameIndexSmall = this.playerList[0].SeatID;
			this.table.gameIndexBig = this.playerList[1%pNum].SeatID;
			this.table.IndexPlayerCurrent = this.playerList[2%pNum].SeatID;

			Console.WriteLine("small: {0} big: {1} current: {2}", this.table.IndexPlayerSmall, this.table.IndexPlayerBig, this.table.IndexPlayerCurrent);

			this.playerList[0].Player.resetGame(Player.GameRoles.Dealer);
			this.playerList[1%pNum].Player.resetGame(Player.GameRoles.Small);
			this.playerList[2%pNum].Player.resetGame(Player.GameRoles.Big);
		}
		private void resetGamePlayers() {
			foreach (Seat s in this.playerList) {
				s.Player.resetGame(Player.GameRoles.Normal);
			}
		}
		private List<Seat> buildPlayerList() {
			List<Seat> pList = new List<Seat>();

			if (this.table.IndexPlayerDealer == 100) {
				ushort loc = 0;
				foreach (Seat s in this.table.playerList) {
					if (s == null) {
						continue;
					}
					loc = s.SeatID;
				}
				this.table.IndexPlayerDealer = Convert.ToUInt16(loc);
			}
			int pMax = this.table.PlayersMax, start = this.table.IndexPlayerDealer % pMax;
			for (int i = (start + 2) % pMax; i != (start + 1) % pMax; i = (i + 1) % pMax) {
				if (this.table.playerList[i] == null) {
					continue;
				}
				pList.Add(this.table.playerList[i]);
			}
			return pList;
		}


		// Game launching point
		private void launchGame() {
			this.GameState = GameStates.NotStarted;
			this.table.publishCurrentGame();
			this.nextPhase();
		}

		// Deal
		public void dealPlayersCards() {
			this.gameState = GameStates.Dealing;
			foreach (Seat s in this.playerList) {
				s.Player.setCards(table.Cards.DrawCard, table.Cards.DrawCard);
				this.sendPlayer(s.Player, MessagesProtocol.HandProtocol(s.Player.Cards));
			}
		}
		public void dealTableCards() {
			this.table.FullTableCards = new Card[] { table.Cards.DrawCard, table.Cards.DrawCard, table.Cards.DrawCard, table.Cards.DrawCard, table.Cards.DrawCard };
		}

		public Boolean isOnGoing() {
			return (this.GameState >= GameStates.Dealing && this.GameState <= GameStates.ShowHands);
		}
		public GameStates GameState {
			get {
				return this.gameState;
			}
			set {
				this.gameState = value;
			}
		}
		public Card[] TableCards {
			get {
				return this.gameCards;
			}
			set {
				this.gameCards = value;
			}
		}
		public ushort IndexPlayerCurrent {
			get {
				return this.table.IndexPlayerCurrent;
			}
		}
		public ushort IndexPlayerDealer {
			get {
				return this.table.IndexPlayerDealer;
			}
		}
		public ushort IndexPlayerSmall {
			get {
				return this.table.IndexPlayerSmall;
			}
		}
		public ushort IndexPlayerBig {
			get {
				return this.table.IndexPlayerBig;
			}
		}

		public Player PlayerTurn {
			get {
				return this.table.playerList[this.IndexPlayerCurrent].Player;
			}
		}
		public void takeStakes() {
			this.table.SeatSmall.Player.PlaceBet(this.table.StakeMin);
			this.table.SeatBig.Player.PlaceBet(this.table.StakeMax);

			this.table.LastRaiseIndex = this.table.IndexPlayerBig;
			this.table.LastRaiseValue = this.table.StakeMax;
			this.table.gameRaiseRoundAmount = this.table.StakeMin + this.table.StakeMax;
		}


		// any new phase
		public void nextPhase() {

			this.table.GameState++;
			this.printFullStatus();
			this.evtNewPhase();

			switch (this.table.GameState) {

				case GameStates.Dealing:
					this.dealPlayersCards();
					this.dealTableCards();
					this.nextPhase();
					break;

				case GameStates.PreFlop:
					this.takeStakes();
					Console.WriteLine("ECHO: " + this.table.SeatDealer);
					Console.WriteLine("ECHO: " + this.table.SeatSmall);
					Console.WriteLine("ECHO: " + this.table.SeatCurrent);
					//this.printFullStatus();
					this.table.publishChipTable();
					this.table.publishCurrentGame();
					this.giveTurnToPlayer(this.table.playerList[IndexPlayerCurrent]);
					break;

				case GameStates.Flop:
				case GameStates.Turn:
				case GameStates.River:
					this.firstRaise = false;
					this.lastAction = GameDecisions.Check;
					this.sendAllPlayers(MessagesProtocol.RevealProtocol(this.GameState, this.table.TableCards));
					this.table.publishChipTable();
					this.table.publishCurrentGame();
					this.giveTurnToFirstPlayerPlaying();
					break;

				case GameStates.ShowHands:
					this.table.publishCurrentGame();
					this.table.endGame();
					break;

				default:
					break;
			}
		}
		public void evtNewPhase() {

			//this.resetPhaseVars();
			this.lastRaise = this.playerList[this.playerList.Count-1].SeatID; // = 100
			this.table.resetPhaseVars();
			// new phase for playing players.
			foreach (Seat s in this.playerList) {
				s.Player.evtNewPhase();
			}
		}


		public ushort findPrevPlayerIndex(ushort seatid) {
			int playerMax = this.table.PlayersMax;
			for (int i = toPositive(seatid - 1, playerMax); i != seatid; i = toPositive(i - 1, playerMax)) {
				if (this.table.playerList[i] == null) {
					continue;
				}
				if (this.table.playerList[i].isPlaying()) {
					return (ushort)i;
				}
			}
			return seatid;
		}
		public int toPositive(int i, int baseN) {
			if (i < 0) { return baseN + i; }
			return i % baseN;
		}

		public void giveTurnToFirstPlayerPlaying() {
			this.giveTurnToPlayer(this.table.playerList[this.findFirstPlayingPlayer().SeatID]);
		}
		public void giveTurnToPlayer(ushort seatid) {
			this.giveTurnToPlayer(this.table.playerList[seatid]);

		}
		public void giveTurnToPlayer(Seat s) {

			//Console.WriteLine("GameEngine.giveTurnToPlayer: " + s.SeatID);
			this.sendAllPlayers(MessagesProtocol.TurnProtocol(s.Player.PlayerName, this.leftToCall(s.Player)));
			this.table.IndexPlayerCurrent = s.SeatID;
			// Start TIMER
		}
		public Seat findFirstPlayingPlayer() {

			foreach (Seat s in this.playerList) {
				if (s.Player.isPlaying) {
					return s;
				}
			}
			return this.playerList[0];
		}
		public void nextPlayer() {
			this.table.IndexPlayerCurrent = this.table.nextPlayingPlayerIndex(this.IndexPlayerCurrent);
			this.giveTurnToPlayer(this.PlayerTurn.Seat);
		}


		public uint leftToCall(Player p) {
			return this.table.gameRaiseRoundAmount - p.gameRoundPot;
		}

		public void canCheck(Player p) {
			//Console.WriteLine("canCheck? {0} lefttocall: {1}", p, leftToCall(p));
			if (this.leftToCall(p) > 0) {
				throw new CannotCheckAfterARaiseGameDecisionException();
			}
		}
		public void canCall(Player p) {
			if (this.leftToCall(p) == 0) {
				throw new NoPlayerHasRaisedGameDecisionException();
			}
		}
		public void canRaise(Seat s, uint value) {
			if (value < s.Player.Chips) {
				if (s.Player.gameRoundPot + value < this.table.LastRaiseValue) {
					throw new RaiseValueMustBeAtLeastBiggerThenRaiseValue();
				}
				if (!s.Player.canBet(value)) {
					throw new NotEnoughtValueTableException();
				}
			}
		}
		public void receivedPlayerAction(Seat s, GameDecisions gd, uint value = 0) {
			this.playerAction(s, gd, value);
			this.checkFurtherActions();
		}

		public void playerAction(Seat s, GameDecisions gd, uint value = 0) {

			Console.WriteLine("PlayerAction: {0}\tAction: {1}\tValue: {2}", s, gd, 0);

			try {
				switch (gd) {
					case GameDecisions.Fold:
						this.playerList.Remove(s);
						break;
					case GameDecisions.Check:
						this.canCheck(s.Player);
						break;
					case GameDecisions.Call:
						this.canCall(s.Player);
						s.Player.PlaceBet(this.leftToCall(s.Player));
						break;
					case GameDecisions.Raise:
						this.canRaise(s, value);
						s.Player.PlaceBet(value);
						this.table.LastRaiseIndex = s.SeatID;
						this.table.LastRaiseValue = value;
						this.table.gameRaiseRoundAmount += value;
						this.firstRaise = false;
						break;
					default:
						break;
				}
				this.lastAction = gd;
				this.table.LastAction = gd;
				this.lastActionOK = true;
				s.playerAction(gd, value);
				this.sendAllPlayers(MessagesProtocol.PlayerActionProtocol(s.Player.PlayerName, s.Player.LastAction, s.Player.LastActionValue));

				//
			}
			catch (GameDecisionException e) {
				this.lastActionOK = false;
				this.sendPlayer(s.Player, MessagesProtocol.ERR_Exception(e));
			}
			//Console.WriteLine("END playerAction" + this.table.IndexPlayerCurrent);
		}

		// method to check whatever to move turn to next player or next game phase
		public void checkFurtherActions() {

			if (!this.lastActionOK) {
				return;
			}

			if (this.playerList.Count <= 1) {
				this.table.endGame();
			}
			else {

				if (this.GameState > GameStates.Dealing && this.lastAction != GameDecisions.Raise) {
					if (this.GameState == GameStates.PreFlop && this.firstRaise) {
						if (this.IndexPlayerCurrent == this.table.LastRaiseIndex) {
							this.nextPhase();
						}
						else {
							this.nextPlayer();
						}
					}
					else if (this.IndexPlayerCurrent == this.findPrevPlayerIndex(this.table.LastRaiseIndex)) {
						this.nextPhase();
					}
					else {
						this.nextPlayer();
					}

				}
			}
		}


		public void evtPlayerQuitOrRanOutOfTime(Player p) {
			if (this.PlayerTurn == p) {
				this.playerAction(p.Seat, GameDecisions.Fold);
			}
			else {
				this.playerList.Remove(p.Seat);
			}
			//this.playerAction(s, GameDecisions.Fold);
		}
		public void playerRanOutOfTime(Seat s) { } // union with evtPlayerQuitOrRanOutOfTime


		// Static functions
		public static GameEngine.GameDecisions StringToGameDecision(string str) {
			GameDecisions gd;

			switch (str.ToUpper()) {
				case "FOLD":
					gd = GameDecisions.Fold;
					break;
				case "CHECK":
					gd = GameDecisions.Check;
					break;
				case "CALL":
					gd = GameDecisions.Call;
					break;
				case "RAISE":
					gd = GameDecisions.Raise;
					break;
				case "ALLIN":
					gd = GameDecisions.AllIn;
					break;
				default:
					throw new NoPlayerActionSelected();
			}
			return gd;
		}


		// Sending methods
		public void sendPlayer(Player p, string data) {
			table.sendPlayer(p, data);
		}
		public void sendAllPlayers(string data) {
			table.sendAllPlayers(data);
		}

		// Printing methods
		public void printPlayingPlayers() {
			Console.WriteLine("Printing PlayingPlayers");
			foreach (Seat s in this.playerList) {
				Console.WriteLine(s.ToString());
			}
			Console.WriteLine("End Printing PlayingPlayers");
		}

		public void printFullStatus() {
			Console.WriteLine("STATUS: Table: {0}\tGameState: {1}\tDealer: {2}\tBig: {3}\tSmall: {4}\tIndex: {5}",
								table.ToString(), gameState, this.IndexPlayerDealer, this.IndexPlayerSmall, this.IndexPlayerBig, this.IndexPlayerCurrent);
			this.printPlayingPlayers();
		}
	}


	// GameEngine Exceptions
	public abstract class GameEngineException : TableException {
		public GameEngineException() { }
		public GameEngineException(string message) : base(message) { }
	}
	public class GameNotStartedGameEngineException : GameEngineException { }
	public class GameIsAlreadyStartedGameEngineException : GameEngineException { }
	public class PlayerTurnGameEngineException : GameEngineException { }
	public class MinimumPlayersGameEngineException : GameEngineException {
		public MinimumPlayersGameEngineException(string message) : base(message) { }
	}

	public class GameDecisionException : GameEngineException {
		public GameDecisionException() { }
		public GameDecisionException(string message) : base(message) { }
	}
	public class NoPlayerActionSelected : GameDecisionException { }
	public class RaiseValueMustBeAtLeastBiggerThenRaiseValue : GameDecisionException { }
	public class NoPlayerHasRaisedGameDecisionException : GameDecisionException { }
	public class CannotCheckAfterARaiseGameDecisionException : GameDecisionException { }
	public class ActionIsIllegalGameDecisionException : GameDecisionException {
		public ActionIsIllegalGameDecisionException(string message) : base(message) { }
	}
}
